Lær at komponere React custom hooks for at abstrahere kompleks logik, forbedre genbrugelighed af kode og øge vedligeholdelsen. Inkluderer eksempler og best practices.
Komposition af Custom Hooks i React: Mestring af Abstraktion af Kompleks Logik
React custom hooks er et kraftfuldt værktøj til at indkapsle og genbruge stateful logik i dine React-applikationer. Men i takt med at dine applikationer vokser i kompleksitet, gør logikken i dine custom hooks det også. Dette kan føre til monolitiske hooks, der er svære at forstå, teste og vedligeholde. Komposition af custom hooks tilbyder en løsning på dette problem ved at give dig mulighed for at nedbryde kompleks logik i mindre, mere håndterbare og genbrugelige hooks.
Hvad er Komposition af Custom Hooks?
Komposition af custom hooks er praksissen med at kombinere flere mindre custom hooks for at skabe mere kompleks funktionalitet. I stedet for at skabe en enkelt, stor hook, der håndterer alt, skaber du flere mindre hooks, der hver især er ansvarlige for et specifikt aspekt af logikken. Disse mindre hooks kan derefter komponeres sammen for at opnå den ønskede funktionalitet.
Tænk på det som at bygge med LEGO-klodser. Hver klods (lille hook) har en specifik funktion, og du kombinerer dem på forskellige måder for at bygge komplekse strukturer (større funktioner).
Fordele ved Komposition af Custom Hooks
- Forbedret Genbrugelighed af Kode: Mindre, mere fokuserede hooks er i sagens natur mere genbrugelige på tværs af forskellige komponenter og endda forskellige projekter.
- Forbedret Vedligeholdelse: Nedbrydning af kompleks logik i mindre, selvstændige enheder gør det lettere at forstå, debugge og ændre din kode. Ændringer i én hook har mindre sandsynlighed for at påvirke andre dele af din applikation.
- Øget Testbarhed: Mindre hooks er lettere at teste isoleret, hvilket fører til mere robust og pålidelig kode.
- Bedre Kodeorganisering: Komposition opfordrer til en mere modulær og organiseret kodebase, hvilket gør det lettere at navigere og forstå forholdet mellem forskellige dele af din applikation.
- Reduceret Kodeduplikering: Ved at udtrække fælles logik i genbrugelige hooks minimerer du kodeduplikering, hvilket fører til en mere koncis og vedligeholdelsesvenlig kodebase.
Hvornår skal man bruge Komposition af Custom Hooks
Du bør overveje at bruge komposition af custom hooks, når:
- En enkelt custom hook bliver for stor og kompleks.
- Du opdager, at du duplikerer lignende logik i flere custom hooks eller komponenter.
- Du ønsker at forbedre testbarheden af dine custom hooks.
- Du ønsker at skabe en mere modulær og genbrugelig kodebase.
Grundlæggende Principper for Komposition af Custom Hooks
Her er nogle nøgleprincipper til at guide din tilgang til komposition af custom hooks:
- Single Responsibility Principle: Hver custom hook bør have et enkelt, veldefineret ansvar. Dette gør dem lettere at forstå, teste og genbruge.
- Separation of Concerns: Adskil forskellige aspekter af din logik i forskellige hooks. For eksempel kan du have en hook til at hente data, en anden til at håndtere state og en tredje til at håndtere sideeffekter.
- Komponérbarhed: Design dine hooks, så de let kan komponeres med andre hooks. Dette indebærer ofte at returnere data eller funktioner, der kan bruges af andre hooks.
- Navngivningskonventioner: Brug klare og beskrivende navne til dine hooks for at angive deres formål og funktionalitet. En almindelig konvention er at sætte `use` foran hook-navne.
Almindelige Kompositionsmønstre
Flere mønstre kan bruges til at komponere custom hooks. Her er nogle af de mest almindelige:
1. Simpel Hook-komposition
Dette er den mest basale form for komposition, hvor en hook blot kalder en anden hook og bruger dens returværdi.
Eksempel: Forestil dig, at du har en hook til at hente brugerdata og en anden til at formatere datoer. Du kan komponere disse hooks for at skabe en ny hook, der henter brugerdata og formaterer brugerens registreringsdato.
import { useState, useEffect } from 'react';
function useUserData(userId) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function fetchData() {
try {
const response = await fetch(`https://api.example.com/users/${userId}`);
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const jsonData = await response.json();
setData(jsonData);
} catch (e) {
setError(e);
} finally {
setLoading(false);
}
}
fetchData();
}, [userId]);
return { data, loading, error };
}
function useFormattedDate(dateString) {
try {
const date = new Date(dateString);
const formattedDate = date.toLocaleDateString(undefined, { year: 'numeric', month: 'long', day: 'numeric' });
return formattedDate;
} catch (error) {
console.error("Error formatting date:", error);
return "Invalid Date";
}
}
function useUserWithFormattedDate(userId) {
const { data, loading, error } = useUserData(userId);
const formattedRegistrationDate = data ? useFormattedDate(data.registrationDate) : null;
return { ...data, formattedRegistrationDate, loading, error };
}
export default useUserWithFormattedDate;
Forklaring:
useUserDatahenter brugerdata fra et API.useFormattedDateformaterer en datostreng til et brugervenligt format. Den håndterer potentielle fejl ved datokonvertering elegant. ArgumentetundefinedtiltoLocaleDateStringbruger brugerens lokale indstillinger til formatering.useUserWithFormattedDatekomponerer begge hooks. Den bruger førstuseUserDatatil at hente brugerdataene. Hvis data er tilgængelige, bruger den derefteruseFormattedDatetil at formatereregistrationDate. Til sidst returnerer den de originale brugerdata sammen med den formaterede dato, loading-status og eventuelle fejl.
2. Hook-komposition med Delt State
I dette mønster deler og modificerer flere hooks den samme state. Dette kan opnås ved hjælp af useContext eller ved at sende state og setter-funktioner mellem hooks.
Eksempel: Forestil dig, at du bygger en flertrinsformular. Hvert trin kunne have sin egen hook til at håndtere trinnets specifikke inputfelter og valideringslogik, men de deler alle en fælles formular-state, der styres af en overordnet hook ved hjælp af useReducer og useContext.
import React, { createContext, useContext, useReducer } from 'react';
// Define the initial state
const initialState = {
step: 1,
name: '',
email: '',
address: ''
};
// Define the actions
const ACTIONS = {
NEXT_STEP: 'NEXT_STEP',
PREVIOUS_STEP: 'PREVIOUS_STEP',
UPDATE_FIELD: 'UPDATE_FIELD'
};
// Create the reducer
function formReducer(state, action) {
switch (action.type) {
case ACTIONS.NEXT_STEP:
return { ...state, step: state.step + 1 };
case ACTIONS.PREVIOUS_STEP:
return { ...state, step: state.step - 1 };
case ACTIONS.UPDATE_FIELD:
return { ...state, [action.payload.field]: action.payload.value };
default:
return state;
}
}
// Create the context
const FormContext = createContext();
// Create a provider component
function FormProvider({ children }) {
const [state, dispatch] = useReducer(formReducer, initialState);
const value = {
state,
dispatch,
nextStep: () => dispatch({ type: ACTIONS.NEXT_STEP }),
previousStep: () => dispatch({ type: ACTIONS.PREVIOUS_STEP }),
updateField: (field, value) => dispatch({ type: ACTIONS.UPDATE_FIELD, payload: { field, value } })
};
return (
{children}
);
}
// Custom hook for accessing the form context
function useFormContext() {
const context = useContext(FormContext);
if (!context) {
throw new Error('useFormContext must be used within a FormProvider');
}
return context;
}
// Custom hook for Step 1
function useStep1() {
const { state, updateField } = useFormContext();
const updateName = (value) => updateField('name', value);
return {
name: state.name,
updateName
};
}
// Custom hook for Step 2
function useStep2() {
const { state, updateField } = useFormContext();
const updateEmail = (value) => updateField('email', value);
return {
email: state.email,
updateEmail
};
}
// Custom hook for Step 3
function useStep3() {
const { state, updateField } = useFormContext();
const updateAddress = (value) => updateField('address', value);
return {
address: state.address,
updateAddress
};
}
export { FormProvider, useFormContext, useStep1, useStep2, useStep3 };
Forklaring:
- En
FormContextoprettes ved hjælp afcreateContextfor at indeholde formularens state og dispatch-funktion. - En
formReducerhåndterer opdateringer af formularens state ved hjælp afuseReducer. Handlinger somNEXT_STEP,PREVIOUS_STEPogUPDATE_FIELDer defineret til at ændre staten. FormProvider-komponenten giver formularkonteksten til sine børn, hvilket gør state og dispatch tilgængelige for alle trin i formularen. Den eksponerer også hjælpefunktioner fornextStep,previousStepogupdateFieldfor at forenkle afsendelse af handlinger.useFormContext-hooken giver komponenter adgang til formularkontekstens værdier.- Hvert trin (
useStep1,useStep2,useStep3) opretter sin egen hook til at håndtere input relateret til sit trin og brugeruseFormContexttil at hente state og dispatch-funktionen for at opdatere den. Hvert trin eksponerer kun de data og funktioner, der er relevante for det pågældende trin, og overholder dermed princippet om énsidigt ansvar.
3. Hook-komposition med Livscyklus-styring
Dette mønster involverer hooks, der styrer forskellige faser af en komponents livscyklus, såsom mounting, opdatering og unmounting. Dette opnås ofte ved at bruge useEffect i de komponerede hooks.
Eksempel: Forestil dig en komponent, der skal spore online/offline-status og også skal udføre noget oprydning, når den unmounts. Du kan oprette separate hooks for hver af disse opgaver og derefter komponere dem.
import { useState, useEffect } from 'react';
function useOnlineStatus() {
const [isOnline, setIsOnline] = useState(navigator.onLine);
useEffect(() => {
function handleOnline() {
setIsOnline(true);
}
function handleOffline() {
setIsOnline(false);
}
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
}, []);
return isOnline;
}
function useDocumentTitle(title) {
useEffect(() => {
document.title = title;
return () => {
document.title = 'Original Title'; // Revert to a default title on unmount
};
}, [title]);
}
function useAppLifecycle(title) {
const isOnline = useOnlineStatus();
useDocumentTitle(title);
return isOnline; // Return the online status
}
export { useAppLifecycle, useOnlineStatus, useDocumentTitle };
Forklaring:
useOnlineStatussporer brugerens online-status ved hjælp afonline- ogoffline-events.useEffect-hooken opsætter event listeners, når komponenten mounter, og rydder op i dem, når den unmounts.useDocumentTitleopdaterer dokumentets titel. Den nulstiller også titlen til en standardværdi, når komponenten unmounts, for at sikre, at der ikke er nogen vedvarende titelproblemer.useAppLifecyclekomponerer begge hooks. Den brugeruseOnlineStatustil at afgøre, om brugeren er online, oguseDocumentTitletil at indstille dokumentets titel. Den kombinerede hook returnerer online-status.
Praktiske Eksempler og Anvendelsestilfælde
1. Internationalisering (i18n)
Håndtering af oversættelser og skift af sprogindstillinger kan blive komplekst. Du kan bruge hook-komposition til at adskille ansvarsområder:
useLocale(): Håndterer den aktuelle sprogindstilling (locale).useTranslations(): Henter og leverer oversættelser for den aktuelle sprogindstilling.useTranslate(key): En hook, der tager en oversættelsesnøgle og returnerer den oversatte streng ved at brugeuseTranslations-hooken til at få adgang til oversættelserne.
Dette giver dig mulighed for let at skifte sprogindstillinger og få adgang til oversættelser i hele din applikation. Overvej at bruge biblioteker som i18next sammen med custom hooks til at håndtere oversættelseslogikken. For eksempel kunne useTranslations indlæse oversættelser baseret på den valgte sprogindstilling fra JSON-filer på forskellige sprog.
2. Formularvalidering
Komplekse formularer kræver ofte omfattende validering. Du kan bruge hook-komposition til at skabe genbrugelig valideringslogik:
useInput(initialValue): Håndterer tilstanden for et enkelt inputfelt.useValidator(value, rules): Validerer et enkelt inputfelt baseret på et sæt regler (f.eks. påkrævet, e-mail, minLength).useForm(fields): Håndterer tilstanden og valideringen af hele formularen ved at komponereuseInputoguseValidatorfor hvert felt.
Denne tilgang fremmer genbrugelighed af kode og gør det lettere at tilføje eller ændre valideringsregler. Biblioteker som Formik eller React Hook Form tilbyder færdigbyggede løsninger, men kan udvides med custom hooks til specifikke valideringsbehov.
3. Datahentning og Caching
Håndtering af datahentning, caching og fejlhåndtering kan forenkles med hook-komposition:
useFetch(url): Henter data fra en given URL.useCache(key, fetchFunction): Cacher resultatet af en fetch-funktion ved hjælp af en nøgle.useData(url, options): KombinereruseFetchoguseCachefor at hente data og cache resultaterne.
Dette giver dig mulighed for let at cache data, der ofte tilgås, og forbedre ydeevnen. Biblioteker som SWR (Stale-While-Revalidate) og React Query tilbyder kraftfulde løsninger til datahentning og caching, der kan udvides med custom hooks.
4. Autentificering
Håndtering af autentificeringslogik kan være kompleks, især når man arbejder med forskellige autentificeringsmetoder (f.eks. JWT, OAuth). Hook-komposition kan hjælpe med at adskille forskellige aspekter af autentificeringsprocessen:
useAuthToken(): Håndterer autentificeringstokenet (f.eks. lagring og hentning fra lokal lagring).useUser(): Henter og leverer den aktuelle brugers oplysninger baseret på autentificeringstokenet.useAuth(): Tilbyder autentificeringsrelaterede funktioner som login, logout og signup ved at komponere de andre hooks.
Denne tilgang giver dig mulighed for let at skifte mellem forskellige autentificeringsmetoder eller tilføje nye funktioner til autentificeringsprocessen. Biblioteker som Auth0 og Firebase Authentication kan bruges som backend til at håndtere brugerkonti og autentificering, og custom hooks kan oprettes for at interagere med disse tjenester.
Best Practices for Komposition af Custom Hooks
- Hold Hooks Fokuserede: Hver hook bør have et klart og specifikt formål.
- Undgå Dyb Nesting: Begræns antallet af kompositionsniveauer for at undgå at gøre din kode svær at forstå. Hvis en hook bliver for kompleks, så overvej at bryde den yderligere ned.
- Dokumenter Dine Hooks: Sørg for klar og koncis dokumentation for hver hook, der forklarer dens formål, input og output. Dette er især vigtigt for hooks, der bruges af andre udviklere.
- Test Dine Hooks: Skriv enhedstests for hver hook for at sikre, at den fungerer korrekt. Dette er især vigtigt for hooks, der håndterer state eller udfører sideeffekter.
- Overvej at Bruge et State Management-bibliotek: Til komplekse state management-scenarier kan du overveje at bruge et bibliotek som Redux, Zustand eller Jotai. Disse biblioteker tilbyder mere avancerede funktioner til håndtering af state og kan forenkle kompositionen af hooks.
- Tænk på Fejlhåndtering: Implementer robust fejlhåndtering i dine hooks for at forhindre uventet adfærd. Overvej at bruge try-catch-blokke til at fange fejl og give informative fejlmeddelelser.
- Overvej Ydeevne: Vær opmærksom på ydeevnekonsekvenserne af dine hooks. Undgå unødvendige re-renders og optimer din kode for ydeevne. Brug React.memo, useMemo og useCallback til at optimere ydeevnen, hvor det er relevant.
Konklusion
Komposition af React custom hooks er en kraftfuld teknik til at abstrahere kompleks logik og forbedre genbrugelighed, vedligeholdelse og testbarhed af kode. Ved at nedbryde komplekse opgaver i mindre, mere håndterbare hooks, kan du skabe en mere modulær og organiseret kodebase. Ved at følge de bedste praksisser, der er beskrevet i denne artikel, kan du effektivt udnytte komposition af custom hooks til at bygge robuste og skalerbare React-applikationer. Husk altid at prioritere klarhed og enkelhed i din kode, og vær ikke bange for at eksperimentere med forskellige kompositionsmønstre for at finde det, der fungerer bedst for dine specifikke behov.